C#实现ModbusRTU详解【六】

您所在的位置:网站首页 modbus slave报文 C#实现ModbusRTU详解【六】

C#实现ModbusRTU详解【六】

2023-10-05 11:12| 来源: 网络整理| 查看: 265

前言

NModbus4提供了直接读写的方法,但是通过那几个方法,我们无法获取NModbus4生成的报文。如果需要获取报文,则需要使用另外一种方式实现读写。

传送门:

C#实现ModbusRTU详解【一】—— 简介及仿真配置

C#实现ModbusRTU详解【二】—— 生成读取报文

C#实现ModbusRTU详解【三】—— 生成写入报文

C#实现ModbusRTU详解【四】—— 通讯Demo

C#实现ModbusRTU详解【五】—— NModbus4的使用

本专栏的代码已上传至GitHub,项目地址如下:

https://github.com/XMNHCAS/ModbusRtuDemo 

ModbusMaster类

之前我们使用NModbus4的时候,使用的是IModbusMaster这个接口来创建通讯实例。代码如下所示:

//创建串口实例 SerialPort sport = new SerialPort("COM11", 9600, Parity.None, 8, StopBits.One); //创建ModbusRTU主站实例 IModbusMaster master = ModbusSerialMaster.CreateRtu(sport);

查看IModbusMaster的定义,可以看到,它仅有四种读取方法和四种写入的同步及异步方法。

实际上,NModbus4中存在一个实现了IModbusMaster接口的类,ModbusMaster,它的定义如下图所示:

可以看到,ModbusMaster这个类中,比IModbusMaster多了一个ExecuteCustomMessage的方法。我们可以通过这个方法来发送我们的自定义报文,并获取接收到的相应报文。

但是这个方法接收的参数和返回的值,均为实现了IModbusMessage接口的类的实例,接下来我们来看看IModbusMessage。 

IModbusMessage接口

IModbusMessage是NModbus4的报文定义接口,它的定义如下:

它定义了五个属性和一个方法,但是我们只需要关注的只前面四个,FunctionCode为功能码,SlaveAddress为从站ID,MessageFrame为除校验码外的报文主体,ProtocolDataUnit为除校验码和从站地址外的报文。

我们使用ModbusMaster的ExecuteCustomMessage方法时,可以自行创建实现该接口的类,来生成对应的请求报文和响应报文。不过NModbus4已经为我们创建好了常用的报文类,所以我们在日常使用的时候,无需再自行定义。如果有兴趣学习如何自行创建实现IModbusMessage的类,可以在GitHub上下载NModbus4的源码自行研究。

以下为NModbus4请求报文和相应报文类:

请求报文响应报文线圈读取Modbus.Message.ReadCoilsInputsRequestModbus.Message.ReadCoilsInputsResponse写入单个Modbus.Message.WriteSingleCoilRequestResponse批量Modbus.Message.WriteMultipleCoilsRequestModbus.Message.WriteMultipleCoilsResponse寄存器读取Modbus.Message.ReadHoldingInputRegistersRequestModbus.Message.ReadHoldingInputRegistersResponse写入单个Modbus.Message.WriteSingleRegisterRequestResponse批量Modbus.Message.WriteMultipleRegistersRequestModbus.Message.WriteMultipleRegistersResponse

与写报文

Modbus.Message.ReadWriteMultipleRegistersRequestReadHoldingInputRegistersResponseWriteMultipleRegistersResponse 读写数据

NModbus4的报文读写的方式,与我们在前面自定义的方式基本相同,同样是通过串口发送我们生成的请求报文以及解析响应报文。

具体实现过程就是先创建对应的请求报文实例,确定从站地址、功能码、读写地址、写入数据等参数,然后使用ModbusMaster实例的ExecuteCustomMessage方法,以请求报文的实例为参数,并规定响应报文的类型,最后获取响应的结果。

读取线圈

通过ReadCoilsInputsRequest类创建对应的请求报文实例,构造函数第一个参数为功能码,第二个为从站地址,第三个为起始地址,第四个为读取的线圈数。

ExecuteCustomMessage规定了返回的值类型为ReadCoilsInputsResponse。

返回的数据的Data属性为读取到的实际数据。

//功能码01 请求报文 ReadCoilsInputsRequest readCoilsReq = new ReadCoilsInputsRequest(0x01, 0x01, 0, 10); //获取响应报文 var readCoilsRes = master.ExecuteCustomMessage(readCoilsReq); //功能码02 请求报文 ReadCoilsInputsRequest readInputCoilsReq = new ReadCoilsInputsRequest(0x02, 0x01, 0, 10); //获取响应报文 var readInputCoilsRes = master.ExecuteCustomMessage(readInputCoilsReq); 读取寄存器

与读取线圈原理相同,此处不多赘述。

//功能码03 请求报文 ReadHoldingInputRegistersRequest readRegistersReq = new ReadHoldingInputRegistersRequest(0x03, 0x01, 0, 10); //获取响应报文 var readRegistersRes = master.ExecuteCustomMessage(readRegistersReq); //功能码04 请求报文 ReadHoldingInputRegistersRequest readInputRegistersReq = new ReadHoldingInputRegistersRequest(0x04, 0x01, 0, 10); //获取响应报文 var readInputRegistersRes = master.ExecuteCustomMessage(readInputRegistersReq); 写入线圈

写入单个线圈时,使用WriteSingleCoilRequestResponse获取请求报文,同样使用该类来获取结果。

而批量写入时则使用WriteMultipleCoilsRequest来获取请求报文。在创建该实例时,需要使用DiscreteCollection来规定写入值。

它们的构造函数的第一个参数是从站地址,第二个参数是写入值的起始地址,第三个参数是写入值。

//写入单个线圈 WriteSingleCoilRequestResponse writeSingleCoilsReq = new WriteSingleCoilRequestResponse(1, 0, true); //获取响应报文 var writeSingleCoilsRes = master.ExecuteCustomMessage(writeSingleCoilsReq); //批量写入线圈 //写入的值 DiscreteCollection writeMultipleCoilsParam = new DiscreteCollection(new List { true, true }); //获取请求报文 WriteMultipleCoilsRequest writeMultipleCoilsReq = new WriteMultipleCoilsRequest(1, 1, writeMultipleCoilsParam); //获取响应报文 var writeMultipleCoilsRes = master.ExecuteCustomMessage(writeMultipleCoilsReq); 写入寄存器

与写入线圈原理基本相同,此处不多赘述。

//写入单个寄存器 WriteSingleRegisterRequestResponse writeSingleRegisterReq = new WriteSingleRegisterRequestResponse(1, 0, 33); //获取响应报文 var writeSingleRegisterRes = master.ExecuteCustomMessage(writeSingleRegisterReq); //批量写入寄存器 //写入的值 RegisterCollection writeMultipleParam = new RegisterCollection(new List { 11, 22 }); //获取请求报文 WriteMultipleRegistersRequest writeMultipleRegistersReq = new WriteMultipleRegistersRequest(1, 1, writeMultipleParam); //获取响应报文 var writeMultipleRegistersRes = master.ExecuteCustomMessage(writeMultipleRegistersReq); 读与写寄存器

读写寄存器还提供了一个特别的类——ReadWriteMultipleRegistersRequest,这个类不能直接作为ExecuteCustomMessage方法的参数,但是这个类有两个属性,分别为ReadRequest和WriteRequest,这两个属性的类型分别为ReadHoldingInputRegistersRequest和WriteMultipleRegistersRequest,也就是寄存器的读写请求报文类。实际上这个类只是对寄存器的读写请求报文进行了进一步的封装。当我们需要对同一个从站的保持型寄存器既执行读取操作,又进行写入操作的时候,就可以使用该类。

该类的构造函数第一个参数为从站地址,第二个参数为起始的读取地址,第三个参数为读取的寄存器数量,第四个参数为起始的写入地址,第五个参数为写入的值。

//写入的值 RegisterCollection rwMultipleParam = new RegisterCollection(new List { 11, 22 }); //获取读写寄存器的读与写的请求报文 ReadWriteMultipleRegistersRequest rwMultipleRegistersReq = new ReadWriteMultipleRegistersRequest(1, 0, 10, 5, rwMultipleParam); //获取读取的结果 var rMultipleRegistersReq = master.ExecuteCustomMessage(rwMultipleRegistersReq.ReadRequest); //获取写入的响应报文 var wMultipleRegistersReq = master.ExecuteCustomMessage(rwMultipleRegistersReq.WriteRequest); 直接执行请求报文

当然我们也可以直接使用我们自己写好的报文来进行发送,下面例子就是使用我们写好的读取报文来进行读取操作。

不过需要注意的是,ExecuteCustomMessage的参数和返回类型都必须实现IModbusMessage接口,所以我们有了报文之后,也依然需要使用ModbusMessageFactory.CreateModbusRequest()方法,来将我们的报文数组转换为实现了IModbusMessage接口的请求报文实例,然后再使用ExecuteCustomMessage。

//01 03 00 00 00 0A C5 CD //读取报文 byte[] readMsg = new byte[] { 0x01, 0x03, 0x00, 0x00, 0x00, 0x0A, 0xC5, 0xCD }; //获取请求报文 var readMsgReq = ModbusMessageFactory.CreateModbusRequest(readMsg); //获取响应报文 var readMsgRes = master.ExecuteCustomMessage(readMsgReq); 完整代码

以下以控制台应用为例,实现NModbus4的报文读写操作。

using Modbus.Data; using Modbus.Device; using Modbus.Message; using System; using System.Collections.Generic; using System.IO.Ports; using System.Linq; using System.Text; namespace NModbus { class Program { static void Main(string[] args) { //串口实例 SerialPort sport = new SerialPort("COM11", 9600, Parity.None, 8, StopBits.One); //NModbus4实例 ModbusMaster master = ModbusSerialMaster.CreateRtu(sport); //打开串口 sport.Open(); #region 读取线圈 //功能码01 请求报文 ReadCoilsInputsRequest readCoilsReq = new ReadCoilsInputsRequest(0x01, 0x01, 0, 10); //获取响应报文 var readCoilsRes = master.ExecuteCustomMessage(readCoilsReq); //输出结果 CWRecvData("读取线圈", readCoilsRes.Data); //功能码02 请求报文 ReadCoilsInputsRequest readInputCoilsReq = new ReadCoilsInputsRequest(0x02, 0x01, 0, 10); //获取响应报文 var readInputCoilsRes = master.ExecuteCustomMessage(readInputCoilsReq); //输出结果 CWRecvData("读取输入线圈", readInputCoilsRes.Data); #endregion #region 读取寄存器 //功能码03 请求报文 ReadHoldingInputRegistersRequest readRegistersReq = new ReadHoldingInputRegistersRequest(0x03, 0x01, 0, 10); //获取响应报文 var readRegistersRes = master.ExecuteCustomMessage(readRegistersReq); //输出结果 CWRecvData("读取保持型寄存器", readRegistersRes.Data); //功能码04 请求报文 ReadHoldingInputRegistersRequest readInputRegistersReq = new ReadHoldingInputRegistersRequest(0x04, 0x01, 0, 10); //获取响应报文 var readInputRegistersRes = master.ExecuteCustomMessage(readInputRegistersReq); //输出结果 CWRecvData("读取输入寄存器", readInputRegistersRes.Data); #endregion #region 写入线圈 //写入单个线圈 WriteSingleCoilRequestResponse writeSingleCoilsReq = new WriteSingleCoilRequestResponse(1, 0, true); //获取响应报文 var writeSingleCoilsRes = master.ExecuteCustomMessage(writeSingleCoilsReq); //输出响应报文 CWRecvData("写入单个线圈的响应报文(无校验码)", writeSingleCoilsRes.SlaveAddress, writeSingleCoilsRes.ProtocolDataUnit); //批量写入线圈 //写入的值 DiscreteCollection writeMultipleCoilsParam = new DiscreteCollection(new List { true, true }); //获取请求报文 WriteMultipleCoilsRequest writeMultipleCoilsReq = new WriteMultipleCoilsRequest(1, 1, writeMultipleCoilsParam); //获取响应报文 var writeMultipleCoilsRes = master.ExecuteCustomMessage(writeMultipleCoilsReq); //输出响应报文 CWRecvData("批量写入线圈的响应报文(无校验码)", writeMultipleCoilsRes.SlaveAddress, writeMultipleCoilsRes.ProtocolDataUnit); #endregion #region 写入寄存器 //写入单个寄存器 WriteSingleRegisterRequestResponse writeSingleRegisterReq = new WriteSingleRegisterRequestResponse(1, 0, 33); //获取响应报文 var writeSingleRegisterRes = master.ExecuteCustomMessage(writeSingleRegisterReq); //输出响应报文 CWRecvData("写入单个寄存器的响应报文(无校验码)", writeSingleRegisterRes.SlaveAddress, writeSingleRegisterRes.ProtocolDataUnit); //批量写入寄存器 //写入的值 RegisterCollection writeMultipleParam = new RegisterCollection(new List { 11, 22 }); //获取请求报文 WriteMultipleRegistersRequest writeMultipleRegistersReq = new WriteMultipleRegistersRequest(1, 1, writeMultipleParam); //获取响应报文 var writeMultipleRegistersRes = master.ExecuteCustomMessage(writeMultipleRegistersReq); //输出响应报文 CWRecvData("批量写入寄存器的响应报文(无校验码)", writeMultipleRegistersRes.SlaveAddress, writeMultipleRegistersRes.ProtocolDataUnit); #endregion #region 读与写寄存器 //写入的值 RegisterCollection rwMultipleParam = new RegisterCollection(new List { 11, 22 }); //获取读写寄存器的读与写的请求报文 ReadWriteMultipleRegistersRequest rwMultipleRegistersReq = new ReadWriteMultipleRegistersRequest(1, 0, 10, 5, rwMultipleParam); //获取读取的结果 var rMultipleRegistersReq = master.ExecuteCustomMessage(rwMultipleRegistersReq.ReadRequest); //输出结果 CWRecvData("读取寄存器读取值", rMultipleRegistersReq.Data); //获取写入的响应报文 var wMultipleRegistersReq = master.ExecuteCustomMessage(rwMultipleRegistersReq.WriteRequest); //输出响应报文 CWRecvData("写入寄存器响应报文(无校验码)", wMultipleRegistersReq.SlaveAddress, wMultipleRegistersReq.ProtocolDataUnit); #endregion #region 直接使用报文读取 //01 03 00 00 00 0A C5 CD //读取报文 byte[] readMsg = new byte[] { 0x01, 0x03, 0x00, 0x00, 0x00, 0x0A, 0xC5, 0xCD }; //获取请求报文 var readMsgReq = ModbusMessageFactory.CreateModbusRequest(readMsg); //获取响应报文 var readMsgRes = master.ExecuteCustomMessage(readMsgReq); //输出结果 CWRecvData("直接使用报文读取", readMsgRes.Data); #endregion //关闭串口 sport.Close(); Console.ReadKey(); } /// /// 将接收到的数据打印到控制台 /// /// /// 内容说明 /// 接收到的数据 public static void CWRecvData(string str, ICollection data) { Console.WriteLine($"{str}:"); foreach (var item in data) { Console.Write($"{item} "); } Console.WriteLine("\n"); } /// /// 显示报文 /// /// 内容说明 /// 从站ID /// 报文主体 public static void CWRecvData(string str, byte slaveID, byte[] msg) { Console.WriteLine($"{str}:"); Console.Write($"{slaveID.ToString("X2")} "); foreach (var item in msg) { Console.Write($"{item.ToString("X2")} "); } Console.WriteLine("\n"); } } }

执行结果:

结尾

本文介绍了如何使用NModbus4生成请求报文及获取响应报文,这些是NModbus4实现ModbusRTU通讯的底层方法,实际应用时,使用普通的几个读写方法即可。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3